package classificator.tree;

import classificator.data.Data;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;

/**La classe modella un generico nodo (fogliare o intermedio) dell'albero di decisione. 
 *
 * @author 467644
 */
public abstract class Node implements Serializable{
    //Contatore dei nodi generati nell'albero 
    static int idNodeCount = 0; 
    //Identificativo numerico del nodo 
    protected int idNode;      
    //Indice nell'array del training set del primo esempio coperto dal nodo corrente 
    protected int beginExampleIndex; 
    //Indice nell'array del training set dell'ultimo esempio coperto dal nodo corrente. 
    //beginExampleIndex e endExampleIndex individuano un sotto-insieme di training. 
    protected int endExampleIndex;
    //Valore dell'entropia calcolata, rispetto all'attributo di classe, nel sottoinsieme di training del nodo. 
    protected float entropy; 
    //Oggetto di tipo Mappa per gestire i valori di frequenza assoluta per ogni valore 
    //di classe, necessaria per il calcolo della entropia. 
    protected HashMap<String, Integer> classValueAbsoluteFrequecy =  new HashMap<String, Integer>();

    /**Costruttore di classe. 
     * Avvalora gli attributi primitivi di classe, istanzia e popola la struttura 
     * modellata dall'oggetto classValueAbsoluteFrequecy ed usa questa per calcolare 
     * l'entropia rispetto all'attributo di classe nel sotto-insieme di training coperto dal nodo. 
     * 
     * @param trainingSet Oggetto di classe Data contenente il training set completo
     * @param beginExampleIndex inizio del sottoinsieme di training coperto dal nodo corrente 
     * @param endExampleIndex fine del sottoinsieme di training coperto dal nodo corrente 
     */
    Node(Data trainingSet, int beginExampleIndex, int endExampleIndex) {
        // assegna un id unico identificatiovo del noto
        idNode = idNodeCount++;
        // mantiene gli indici di inizio-fine per la partizione dati
        // compresa tra beginExampleIndex e endExampleIndex
        this.beginExampleIndex=beginExampleIndex;
        this.endExampleIndex=endExampleIndex;

        // aggiorna classValueAbsoluteFrequecy in maniera che per ciascun valore distinto 
        // di classe sia mantenua la frequenza della classe nella partiziane dati
        // compresa tra beginExampleIndex e endExampleIndex
        for(int i=0; i<trainingSet.getClassAttribute().getNumberOfDistinctValues(); i++)
            classValueAbsoluteFrequecy.put(trainingSet.getClassAttribute().getValue(i),0);

        for(int i=beginExampleIndex; i<=endExampleIndex; i++){
            String classValue = trainingSet.getClassValue(i);
            classValueAbsoluteFrequecy.put(classValue, classValueAbsoluteFrequecy.get(classValue)+1);
        }	
        // calcola il valore di entropia per la partizione dati
        // compresa tra beginExampleIndex e endExampleIndex
        entropy = 0;
        int numberOfExamples = endExampleIndex-beginExampleIndex+1;
        
        Iterator<Integer> it = classValueAbsoluteFrequecy.values().iterator();
        while(it.hasNext()){
            int v = it.next();
            if (v!=0){
                float p = ((float) v)/numberOfExamples;
                entropy += ( -p * Math.log10(p) / Math.log10(2));
            }
        }
    }
    
    /**Restituisce il valore del membro idNode 
     * 
     * @return identificativo numerico del nodo 
     */
    int getIdNode() {
        return idNode;
    }

    /**Restituisce il valore del membro beginExampleIndex
     * 
     * @return indice del primo esempio del sotto-insieme rispetto al training set complessivo 
     */
    int getBeginExampleIndex() {
        return beginExampleIndex;
    }
    
    /**Restituisce il valore del membro endExampleIndex 
     * 
     * @return indice dell'ultimo esempio del sotto-insieme rispetto al training set complessivo 
     */
    int getEndExampleIndex() {
        return endExampleIndex;
    }

    /**Restituisce il valore del membro entropy 
     * 
     * @return Valore dell'entropia rispetto al nodo corrente
     */
    float getEntropy() {
        return entropy;
    }
    
    /**E' un metodo astratto. 
     * L'implementazione è rimandata ai nodi di tipo test (split node) dai quali 
     * si possono generare figli, uno per ogni split prodotto. 
     * Restituisce il numero di tali nodi figli. 
     * 
     * @return Valore del numero di nodi figli del nodo corrente. 
     */
    public abstract int getNumberOfChildren(); 

    /**Concatena in un oggetto String i  valori di beginExampleIndex, endExampleIndex, entropy 
     * 
     * @return Stringa che visualizza beginExampleIndex, endExampleIndex, entropy 
     * opportunamente formattati. 
     */
    @Override
    public String toString(){
        return "[Examples:" + beginExampleIndex + ":" + endExampleIndex + "] entropy:" + entropy;
    }
}